﻿using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Collections.Generic;

namespace IMMeDotNet {

	public partial class Host : IDisposable {

		// Stores the current connections.
		private List<Connection> connections = new List<Connection>();

		private Connection GetConnection(byte index) {
			if (index < connections.Count) {
				return connections[index];
			} else {
				return null;
			}
		}

		#region USB Device Access

		/// <summary>
		/// Vendor ID of the USB wireless adaptor.
		/// </summary>
		const ushort VendorID = 0x0E4C;

		/// <summary>
		/// Product ID of the USB wireless adaptor.
		/// </summary>
		const ushort ProductID = 0x7272;

		/// <summary>
		/// Gets a full device path to the USB wireless adaptor.
		/// </summary>
		/// <returns>The full device path, or null if the device cannot be found.</returns>
		static string GetDevicePath() {

			// Create a string that will match against the correct device path.
			var matchingIds = string.Format("vid_{0:x4}&pid_{1:x4}", VendorID, ProductID);

			// Start by getting the HID class.
			Guid hidGuid; Win32.HidD_GetHidGuid(out hidGuid);

			// Get a device information set for HID peripherals.
			var hidInformation = Win32.SetupDiGetClassDevs(ref hidGuid, null, IntPtr.Zero, Win32.DIGCF.DIGCF_PRESENT | Win32.DIGCF.DIGCF_DEVICEINTERFACE);

			try {
				// Storage for the device interface data.
				Win32.SP_DEVICE_INTERFACE_DATA devInterfaceData = new Win32.SP_DEVICE_INTERFACE_DATA();
				devInterfaceData.cbSize = Marshal.SizeOf(devInterfaceData);

				// Enumerate over each device interface.
				uint deviceIndex = 0;
				while (Win32.SetupDiEnumDeviceInterfaces(hidInformation, IntPtr.Zero, ref hidGuid, deviceIndex++, ref devInterfaceData)) {

					// Storage for the device info data.
					Win32.SP_DEVINFO_DATA devInfoData = new Win32.SP_DEVINFO_DATA();
					devInfoData.cbSize = Marshal.SizeOf(devInfoData);

					// Storage for the interface detail data.
					Win32.SP_DEVICE_INTERFACE_DETAIL_DATA devInterfaceDetailData = new Win32.SP_DEVICE_INTERFACE_DETAIL_DATA();
					devInterfaceDetailData.cbSize = (IntPtr.Size == 8) ? 8 : 4 + Marshal.SystemDefaultCharSize;

					// Try to get detailed information about the device interface.
					uint requiredSize;
					if (Win32.SetupDiGetDeviceInterfaceDetail(hidInformation, ref devInterfaceData, ref devInterfaceDetailData, (uint)Marshal.SizeOf(devInterfaceDetailData), out requiredSize, ref devInfoData)) {
						// Does the device path for this interface contain our matching VID/PID pair?
						if (devInterfaceDetailData.DevicePath.ToLowerInvariant().Contains(matchingIds)) return devInterfaceDetailData.DevicePath;
					}
				}
			} finally {
				// Destroy the HID information list.
				Win32.SetupDiDestroyDeviceInfoList(hidInformation);
			}

			// Path not found.
			return null;
		}

		
		#endregion

		#region Constructor

		/// <summary>
		/// Stores the <see cref="FileStream"/> that is used to send and receive data from the USB wireless adaptor.
		/// </summary>
		FileStream wirelessAdaptorStream;

		/// <summary>
		/// Represents the capabilities of the wireless adaptor HID.
		/// </summary>
		Win32.HIDP_CAPS wirelessAdaptorCapabilities;

		/// <summary>
		/// Creates an instance of the <see cref="IMMe"/> class.
		/// </summary>
		public Host() {
			// Try to open the device.
			var win32Handle = Win32.CreateFile(GetDevicePath(), Win32.GENERIC_ACCESS.GENERIC_READ | Win32.GENERIC_ACCESS.GENERIC_WRITE, FileShare.None, IntPtr.Zero, FileMode.Open, (FileAttributes)0x40000000, IntPtr.Zero);
			if (win32Handle.IsInvalid) throw new FileNotFoundException("Could not open wireless adaptor.");

			try {

				// Get device caps.
				IntPtr preparsedData = IntPtr.Zero;
				if (Win32.HidD_GetPreparsedData(win32Handle, ref preparsedData)) {
					try {
						this.wirelessAdaptorCapabilities = new Win32.HIDP_CAPS();
						if (Win32.HidP_GetCaps(preparsedData, ref this.wirelessAdaptorCapabilities) != 0) {
							this.wirelessAdaptorStream = new FileStream(win32Handle, FileAccess.ReadWrite, this.wirelessAdaptorCapabilities.InputReportByteLength, true);
						} else {
							throw new Exception("Could not get wireless adaptor capabilties.");
						}
					} finally {
						// Free the preparsed data block.
						Win32.HidD_FreePreparsedData(ref preparsedData);
					}
				} else {
					throw new Exception("Could not get wireless adaptor capabilties.");
				}

			} catch {
				Win32.CloseHandle(win32Handle);
				throw;
			}

			// Add a dummy connection.
			this.connections.Add(null);

			// Start reading.
			#if !POLLED_USB_INPUT
			this.BeginRead();
			#endif
		}

		#endregion	

		#region IDisposable Members

		protected virtual void Dispose(bool disposing) {
			if (this.wirelessAdaptorStream != null) {
				this.wirelessAdaptorStream.Close();
				this.wirelessAdaptorStream.Dispose();
				this.wirelessAdaptorStream = null;
			}
		}

		public void Dispose() {
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		~Host() {
			this.Dispose(false);
		}

		#endregion
	}
}
